package org.plsql.visitor;

import java.util.HashMap;
import java.util.Map;

import org.antlr.generated.PlSqlParser;
import org.antlr.generated.PlSqlParser.ConcatenationContext;
import org.antlr.generated.PlSqlParser.Declare_specContext;
import org.antlr.generated.PlSqlParser.Is_or_asContext;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.plsql.PlSqlParserTree;
import org.plsql.utils.PlSqlUtils;

public class StringVisitor extends PlSqlRuleVisitor {

    // default
    private String constantFormat = "   %-32s CONSTANT VARCHAR2%-5s := %s;\n";

    public void setConstantFormat(String constantFormat) {
        this.constantFormat = constantFormat + "\n";
        System.out.println("Constant format: " + constantFormat);
    }

    public StringVisitor(PlSqlParserTree tree) {
        super(tree);
    }

    // токен, куда размещать все найденные строки
    // будет или в DECLARE или в начале пакета IS | AS
    private Token declareToken;

    // здесь будет блок констант в виде текста
    private StringBuilder declares;
    private HashMap<String, String> constants = new HashMap<String, String>();

    // ищет ближаюшую функцию или процедуру, чтобы узнать, где используется константа (для удобства логгирования)
    public ParserRuleContext findFunctionOrProcedure(ParserRuleContext ctx) {
        ParserRuleContext c = ctx;

        while (c != null) {
            if ((c.start.getType() == PlSqlParser.FUNCTION) || (c.start.getType() == PlSqlParser.PROCEDURE)) {
                return c.getChild(ParserRuleContext.class, 0);
            }
            c = (ParserRuleContext) c.parent;
        }

        return null;
    }

    // создание блока констант
    public String createConstants() {
        declares = new StringBuilder();

        String inDeclare, constantName, originalText;

        declares.insert(0, "\n   --- autogenerated constants ---\n");
        for (Map.Entry<String, String> e : constants.entrySet()) {
            constantName = e.getKey();
            originalText = e.getValue();

            inDeclare = String.format(constantFormat, constantName, "(" + Integer.toString(originalText.length()) + ")", originalText, 0);
            declares.append(inDeclare);
        }

        declares.append("   --- autogenerated constants ---\n");

        // записывает полученный блок констант в исходник
        rewriter.insertAfter(declareToken, declares);

        return declares.toString();
    }

    // Поиск первого упоминания DECLARE
    @Override
    public Void visitDeclare_spec(Declare_specContext ctx) {
        if (declareToken == null) {
            ParserRuleContext parentRule = (ParserRuleContext) ctx.parent;
            declareToken = parentRule.start;
            System.out.println("  DECLARE => " + declareToken.getText());
        }
        return super.visitDeclare_spec(ctx);
    }

    // Поиск первого упоминания AS или IS
    @Override
    public Void visitIs_or_as(Is_or_asContext ctx) {
        if (declareToken == null) {
            declareToken = ctx.start;
            System.out.println(" DECLARE => " + declareToken.getText());
        }
        return super.visitIs_or_as(ctx);
    }

    // Сжатие конкатенаций в одну строку
    @Override
    public Void visitConcatenation(ConcatenationContext ctx) {
        Void res = super.visitConcatenation(ctx);

        PlSqlUtils.logCharContext("# :concatenation: ", ctx);

        System.out.println("- original: " + PlSqlUtils.getOriginalText(input, ctx)); // CHAR_STRING

        // System.out.println("-- " + ctx.start.getText() + " : " +

        if ((ctx.getChildCount() == 1) && (ctx.start.getType() == PlSqlParser.CHAR_STRING)) {
            // все остальные товарищи (могут быть и цифры, потому, надо
            // проверять тип)

            // все это нужно пихать константами в DECLARE
            String originalText = PlSqlUtils.unquote(PlSqlUtils.getOriginalText(input, ctx));

            if (!originalText.isEmpty()) {
                ParserRuleContext fnName = findFunctionOrProcedure(ctx);
                String constantName = PlSqlUtils.makeOraIdentifier(originalText);

                originalText = String.format("'%s' /* %s */", originalText, fnName != null ? fnName.start.getText() : "");

                System.out.println("==> " + constantName + " :: " + originalText);

                String existedValue = constants.get(constantName);

                // новая константа
                if (existedValue == null) {
                    constants.put(constantName, originalText);
                } else {
                    // константа есть, но значение отличается
                    if (!existedValue.equals(constantName)) {
                        int i = 1;
                        // обрезает, чтобы вставить номер
                        constantName = constantName.substring(0, constantName.length() >= 28 ? 28 : constantName.length());

                        // поиск похожих названий
                        for (String key : constants.keySet()) {
                            if (constantName.equals(key.subSequence(0, key.length() >= 28 ? 28 : key.length()))) {
                                i++;
                            }
                        }
                        constantName += Integer.toString(i);
                        constants.put(constantName, originalText);
                    } else {
                        // дубликат - просто добавит комментарий
                        constants.replace(constantName, originalText + "' /* line: " + ctx.start.getLine() + " */");
                    }
                }

                // заменяет литерал на имя константы
                rewriter.replace(ctx.start, ctx.stop, constantName);
            }
        } else {
            System.out.println("- skip: " + ctx.getText());
        }

        return res;
    }
}
